import {DeviceTabs} from '@site/src/react-luma';
import {TransformFeedbackExample} from '@site/src/examples';

# Transform Feedback

This tutorial demonstrates animating geometry using transform feedback via the `BufferTransform` class. A compute shader rotates triangle vertices on the GPU each frame.

:::caution
Tutorials are maintained on a best-effort basis and may not be fully up to date (contributions welcome).
:::

<DeviceTabs />
<TransformFeedbackExample />

It is assumed you've set up your development environment as described in [Setup](/docs/tutorials).

Transform feedback allows us to capture vertex shader results from one pass and use them in subsequent passes. It is a powerful tool that can be used to set up massively parrallelized animations or data transformations. Note that transform feedback can only be used with WebGL 2.

Two buffers store the triangle's positions. `BufferTransform` runs a small
vertex shader that multiplies each position by a rotation matrix and writes the
result into the alternate buffer. After the transform step the buffers swap and
the scene is rendered with the updated positions.


The complete source for this example is shown below:

```typescript
// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {Buffer} from '@luma.gl/core';
import {AnimationLoopTemplate, AnimationProps, Model, Swap, BufferTransform} from '@luma.gl/engine';

const transformVs = /* glsl */ `\
#version 300 es
#define SIN2 0.03489949
#define COS2 0.99939082

mat2 rotation = mat2(
  COS2, SIN2,
  -SIN2, COS2
);

in vec2 oldPositions;
out vec2 newPositions;

void main() {
  newPositions = rotation * oldPositions;
}
`;

const renderVs = /* glsl */ `\
#version 300 es

in vec2 position;
in vec3 color;
out vec3 vColor;

void main() {
    vColor = color;
    gl_Position = vec4(position, 0.0, 1.0);
}
`;

const renderFs = /* glsl */ `\
#version 300 es
precision highp float;

in vec3 vColor;
out vec4 fragColor;

void main() {
    fragColor = vec4(vColor, 1.0);
}
`;

class AppAnimationLoopTemplate extends AnimationLoopTemplate {
  transform: BufferTransform;
  model: Model;

  positionBuffers: Swap<Buffer>;
  colorBuffer: Buffer;

  constructor({device, animationLoop}: AnimationProps) {
    super();

    if (device.type !== 'webgl') {
      throw new Error('This demo is only implemented for WebGL2');
    }

    this.positionBuffers = new Swap({
      current: device.createBuffer(new Float32Array([-0.5, -0.5, 0.5, -0.5, 0.0, 0.5])),
      next: device.createBuffer(new Float32Array(6))
    });

    this.colorBuffer = device.createBuffer(
      new Float32Array([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0])
    );

    this.transform = new BufferTransform(device, {
      vs: transformVs,
      bufferLayout: [{name: 'oldPositions', format: 'float32x2'}],
      outputs: ['newPositions'],
      vertexCount: 3
    });

    this.model = new Model(device, {
      vs: renderVs,
      fs: renderFs,
      attributes: {color: this.colorBuffer},
      bufferLayout: [
        {name: 'position', format: 'float32x2'},
        {name: 'color', format: 'float32x3'}
      ],
      vertexCount: 3
    });
  }

  onFinalize() {
    this.transform.destroy();
    this.model.destroy();
    this.positionBuffers.destroy();
    this.colorBuffer.destroy();
  }

  onRender({device}) {
    // Run a rotation step
    this.transform.run({
      inputBuffers: {oldPositions: this.positionBuffers.current},
      outputBuffers: {newPositions: this.positionBuffers.next}
    });
    this.positionBuffers.swap();

    // Render with the latest positions
    const renderPass = device.beginRenderPass({clearColor: [0, 0, 0, 1]});
    this.model.setAttributes({position: this.positionBuffers.current});
    this.model.draw(renderPass);
    renderPass.end();
  }
}

const animationLoop = makeAnimationLoop(AnimationLoopTemplate, {adapters: [webgl2Adapter]})
animationLoop.start();
```

Keeping the rotation logic in a transform feedback pass avoids CPU involvement
and lets the triangle animate smoothly as GPU buffers swap each frame.
